package com.wind.cloud.common.lock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.util.Assert;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

/**
 * redis乐观锁实现
 *
 * @author wind.
 */
public class SimpleRedisLock implements ILock {

    private static final String PRE = "lock:";

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static RedisScript<Long> redisLockScript;
    private static RedisScript<Long> redisUnLockScript;

    @Override
    public boolean tryLock(String key, String val) {
        return tryLock(key, val, 60000L);
    }

    @Override
    public boolean tryLock(String key, String val, long expireMills) {
        Assert.hasText(key, "key不能为空");
        Assert.hasText(val, "value不能为空");
        Assert.isTrue(expireMills > 0, "过期时间必需大于0");

        List<String> keys = Collections.singletonList(lockKey(key));

        Long result = stringRedisTemplate.execute(getLockScript(), keys, val, String.valueOf(expireMills));
        return result != null && result == 1;
    }

    @Override
    public boolean lock(String key, String val, long timeoutMills) {
        return lock(key, val, 60000L, timeoutMills);
    }

    @Override
    public boolean lock(String key, String val, long expireMills, long timeoutMills) {
        long timeout = timeoutMills;
        while (timeout >= 0) {
            if (tryLock(key, val, expireMills)) {
                return true;
            }

            // 随机时间避免频率相同申请锁，减少竞争
            int sleepTime = ThreadLocalRandom.current().nextInt(100, 200);
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                break;
            }
            timeout -= sleepTime;

        }
        return false;
    }

    @Override
    public boolean unLock(String key, String val) {
        Assert.hasText(key, "key不能为空");
        Assert.hasText(val, "value不能为空");

        List<String> keys = Collections.singletonList(lockKey(key));

        Long result = stringRedisTemplate.execute(getUnLockScript(), keys, val);
        return result != null && result == 1;
    }

    private String lockKey(String key) {
        return PRE + key;
    }

    private RedisScript<Long> getLockScript() {
        if (redisLockScript == null) {
            DefaultRedisScript<Long> script = new DefaultRedisScript<>();
            script.setLocation(new ClassPathResource("lua/SimpleRedisLock.lua"));
            script.setResultType(Long.class);
            redisLockScript = script;
        }
        return redisLockScript;
    }

    private RedisScript<Long> getUnLockScript() {
        if (redisUnLockScript == null) {
            DefaultRedisScript<Long> script = new DefaultRedisScript<>();
            script.setLocation(new ClassPathResource("lua/SimpleRedisUnLock.lua"));
            script.setResultType(Long.class);
            redisUnLockScript = script;
        }
        return redisUnLockScript;
    }
}
